/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.0 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Shader built-in variable tests.
 *//*--------------------------------------------------------------------*/

#include "es3fShaderBuiltinVarTests.hpp"
#include "glsShaderRenderCase.hpp"
#include "glsShaderExecUtil.hpp"
#include "deRandom.hpp"
#include "deString.h"
#include "deMath.h"
#include "deUniquePtr.hpp"
#include "deStringUtil.hpp"
#include "tcuTestLog.hpp"
#include "tcuTestCase.hpp"
#include "tcuTextureUtil.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuImageCompare.hpp"
#include "gluPixelTransfer.hpp"
#include "gluDrawUtil.hpp"
#include "gluStrUtil.hpp"
#include "rrRenderer.hpp"
#include "rrFragmentOperations.hpp"

#include "glwEnums.hpp"
#include "glwFunctions.hpp"

using std::string;
using std::vector;
using tcu::TestLog;

namespace deqp
{
namespace gles3
{
namespace Functional
{

static int getInteger (const glw::Functions& gl, deUint32 pname)
{
	int value = -1;
	gl.getIntegerv(pname, &value);
	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
	return value;
}

template<deUint32 Pname>
static int getInteger (const glw::Functions& gl)
{
	return getInteger(gl, Pname);
}

static int getVectorsFromComps (const glw::Functions& gl, deUint32 pname)
{
	int value = -1;
	gl.getIntegerv(pname, &value);
	GLU_EXPECT_NO_ERROR(gl.getError(), ("glGetIntegerv(" + glu::getGettableStateStr((int)pname).toString() + ")").c_str());
	TCU_CHECK_MSG(value%4 == 0, ("Expected " + glu::getGettableStateStr((int)pname).toString() + " to be divisible by 4").c_str());
	return value/4;
}

template<deUint32 Pname>
static int getVectorsFromComps (const glw::Functions& gl)
{
	return getVectorsFromComps(gl, Pname);
}

class ShaderBuiltinConstantCase : public TestCase
{
public:
	typedef int (*GetConstantValueFunc) (const glw::Functions& gl);

								ShaderBuiltinConstantCase	(Context& context, const char* name, const char* desc, const char* varName, GetConstantValueFunc getValue, glu::ShaderType shaderType);
								~ShaderBuiltinConstantCase	(void);

	IterateResult				iterate						(void);

private:
	const std::string			m_varName;
	const GetConstantValueFunc	m_getValue;
	const glu::ShaderType		m_shaderType;
};

ShaderBuiltinConstantCase::ShaderBuiltinConstantCase (Context& context, const char* name, const char* desc, const char* varName, GetConstantValueFunc getValue, glu::ShaderType shaderType)
	: TestCase		(context, name, desc)
	, m_varName		(varName)
	, m_getValue	(getValue)
	, m_shaderType	(shaderType)
{
}

ShaderBuiltinConstantCase::~ShaderBuiltinConstantCase (void)
{
}

static gls::ShaderExecUtil::ShaderExecutor* createGetConstantExecutor (const glu::RenderContext& renderCtx, glu::ShaderType shaderType, const std::string& varName)
{
	using namespace gls::ShaderExecUtil;

	ShaderSpec	shaderSpec;

	shaderSpec.version	= glu::GLSL_VERSION_300_ES;
	shaderSpec.source	= string("result = ") + varName + ";\n";
	shaderSpec.outputs.push_back(Symbol("result", glu::VarType(glu::TYPE_INT, glu::PRECISION_HIGHP)));

	return createExecutor(renderCtx, shaderType, shaderSpec);
}

ShaderBuiltinConstantCase::IterateResult ShaderBuiltinConstantCase::iterate (void)
{
	using namespace gls::ShaderExecUtil;

	const de::UniquePtr<ShaderExecutor>	shaderExecutor	(createGetConstantExecutor(m_context.getRenderContext(), m_shaderType, m_varName));
	const int							reference		= m_getValue(m_context.getRenderContext().getFunctions());
	int									result			= -1;
	void* const							outputs			= &result;

	if (!shaderExecutor->isOk())
	{
		shaderExecutor->log(m_testCtx.getLog());
		TCU_FAIL("Compile failed");
	}

	shaderExecutor->useProgram();
	shaderExecutor->execute(1, DE_NULL, &outputs);

	m_testCtx.getLog() << TestLog::Integer(m_varName, m_varName, "", QP_KEY_TAG_NONE, result);

	if (result != reference)
	{
		m_testCtx.getLog() << TestLog::Message << "ERROR: Expected " << m_varName << " = " << reference << TestLog::EndMessage
						   << TestLog::Message << "Test shader:" << TestLog::EndMessage;
		shaderExecutor->log(m_testCtx.getLog());
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid builtin constant value");
	}
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	return STOP;
}

namespace
{

struct DepthRangeParams
{
	DepthRangeParams (void)
		: zNear	(0.0f)
		, zFar	(1.0f)
	{
	}

	DepthRangeParams (float zNear_, float zFar_)
		: zNear	(zNear_)
		, zFar	(zFar_)
	{
	}

	float	zNear;
	float	zFar;
};

class DepthRangeEvaluator : public gls::ShaderEvaluator
{
public:
	DepthRangeEvaluator (const DepthRangeParams& params)
		: m_params(params)
	{
	}

	void evaluate (gls::ShaderEvalContext& c)
	{
		float zNear	= deFloatClamp(m_params.zNear, 0.0f, 1.0f);
		float zFar	= deFloatClamp(m_params.zFar, 0.0f, 1.0f);
		float diff	= zFar - zNear;
		c.color.xyz() = tcu::Vec3(zNear, zFar, diff*0.5f + 0.5f);
	}

private:
	const DepthRangeParams& m_params;
};

} // anonymous

class ShaderDepthRangeTest : public gls::ShaderRenderCase
{
public:
	ShaderDepthRangeTest (Context& context, const char* name, const char* desc, bool isVertexCase)
		: ShaderRenderCase	(context.getTestContext(), context.getRenderContext(), context.getContextInfo(), name, desc, isVertexCase, m_evaluator)
		, m_evaluator		(m_depthRange)
		, m_iterNdx			(0)
	{
	}

	void init (void)
	{
		static const char* defaultVertSrc =
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n";
		static const char* defaultFragSrc =
			"#version 300 es\n"
			"in mediump vec4 v_color;\n"
			"layout(location = 0) out mediump vec4 o_color;\n\n"
			"void main (void)\n"
			"{\n"
			"	o_color = v_color;\n"
			"}\n";

		// Construct shader.
		std::ostringstream src;
		src << "#version 300 es\n";
		if (m_isVertexCase)
			src << "in highp vec4 a_position;\n"
				<< "out mediump vec4 v_color;\n";
		else
			src << "layout(location = 0) out mediump vec4 o_color;\n";

		src << "void main (void)\n{\n";
		src << "\t" << (m_isVertexCase ? "v_color" : "o_color") << " = vec4(gl_DepthRange.near, gl_DepthRange.far, gl_DepthRange.diff*0.5 + 0.5, 1.0);\n";

		if (m_isVertexCase)
			src << "\tgl_Position = a_position;\n";

		src << "}\n";

		m_vertShaderSource		= m_isVertexCase ? src.str()		: defaultVertSrc;
		m_fragShaderSource		= m_isVertexCase ? defaultFragSrc	: src.str();

		gls::ShaderRenderCase::init();
	}

	IterateResult iterate (void)
	{
		const glw::Functions& gl = m_renderCtx.getFunctions();

		const DepthRangeParams cases[] =
		{
			DepthRangeParams(0.0f,  1.0f),
			DepthRangeParams(1.5f, -1.0f),
			DepthRangeParams(0.7f,  0.3f)
		};

		m_depthRange = cases[m_iterNdx];
		m_testCtx.getLog() << tcu::TestLog::Message << "glDepthRangef(" << m_depthRange.zNear << ", " << m_depthRange.zFar << ")" << tcu::TestLog::EndMessage;
		gl.depthRangef(m_depthRange.zNear, m_depthRange.zFar);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glDepthRangef()");

		gls::ShaderRenderCase::iterate();
		m_iterNdx += 1;

		if (m_iterNdx == DE_LENGTH_OF_ARRAY(cases) || m_testCtx.getTestResult() != QP_TEST_RESULT_PASS)
			return STOP;
		else
			return CONTINUE;
	}

private:
	DepthRangeParams		m_depthRange;
	DepthRangeEvaluator		m_evaluator;
	int						m_iterNdx;
};

class FragCoordXYZCase : public TestCase
{
public:
	FragCoordXYZCase (Context& context)
		: TestCase(context, "fragcoord_xyz", "gl_FragCoord.xyz Test")
	{
	}

	IterateResult iterate (void)
	{
		TestLog&				log			= m_testCtx.getLog();
		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
		const int				width		= m_context.getRenderTarget().getWidth();
		const int				height		= m_context.getRenderTarget().getHeight();
		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();
		const tcu::Vec3			scale		(1.f / float(width), 1.f / float(height), 1.0f);

		tcu::Surface			testImg		(width, height);
		tcu::Surface			refImg		(width, height);

		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n",

			"#version 300 es\n"
			"uniform highp vec3 u_scale;\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color = vec4(gl_FragCoord.xyz*u_scale, 1.0);\n"
			"}\n"));

		log << program;

		if (!program.isOk())
			throw tcu::TestError("Compile failed");

		// Draw with GL.
		{
			const float positions[] =
			{
				-1.0f,  1.0f, -1.0f, 1.0f,
				-1.0f, -1.0f,  0.0f, 1.0f,
				 1.0f,  1.0f,  0.0f, 1.0f,
				 1.0f, -1.0f,  1.0f, 1.0f
			};
			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };

			const int				scaleLoc	= gl.getUniformLocation(program.getProgram(), "u_scale");
			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);

			gl.useProgram(program.getProgram());
			gl.uniform3fv(scaleLoc, 1, scale.getPtr());

			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));

			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
		}

		// Draw reference
		for (int y = 0; y < refImg.getHeight(); y++)
		{
			for (int x = 0; x < refImg.getWidth(); x++)
			{
				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
				const float			z			= (xf + yf) / 2.0f;
				const tcu::Vec3		fragCoord	(float(x)+.5f, float(y)+.5f, z);
				const tcu::Vec3		scaledFC	= fragCoord*scale;
				const tcu::Vec4		color		(scaledFC.x(), scaledFC.y(), scaledFC.z(), 1.0f);

				refImg.setPixel(x, y, tcu::RGBA(color));
			}
		}

		// Compare
		{
			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									isOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}
};

static inline float projectedTriInterpolate (const tcu::Vec3& s, const tcu::Vec3& w, float nx, float ny)
{
	return (s[0]*(1.0f-nx-ny)/w[0] + s[1]*ny/w[1] + s[2]*nx/w[2]) / ((1.0f-nx-ny)/w[0] + ny/w[1] + nx/w[2]);
}

class FragCoordWCase : public TestCase
{
public:
	FragCoordWCase (Context& context)
		: TestCase(context, "fragcoord_w", "gl_FragCoord.w Test")
	{
	}

	IterateResult iterate (void)
	{
		TestLog&				log			= m_testCtx.getLog();
		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
		const int				width		= m_context.getRenderTarget().getWidth();
		const int				height		= m_context.getRenderTarget().getHeight();
		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();

		tcu::Surface			testImg		(width, height);
		tcu::Surface			refImg		(width, height);

		const float				w[4]		= { 1.7f, 2.0f, 1.2f, 1.0f };

		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n",

			"#version 300 es\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color = vec4(0.0, 1.0/gl_FragCoord.w - 1.0, 0.0, 1.0);\n"
			"}\n"));

		log << program;

		if (!program.isOk())
			throw tcu::TestError("Compile failed");

		// Draw with GL.
		{
			const float positions[] =
			{
				-w[0],  w[0], 0.0f, w[0],
				-w[1], -w[1], 0.0f, w[1],
				 w[2],  w[2], 0.0f, w[2],
				 w[3], -w[3], 0.0f, w[3]
			};
			const deUint16 indices[] = { 0, 1, 2, 2, 1, 3 };

			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);

			gl.useProgram(program.getProgram());

			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indices), &indices[0]));

			glu::readPixels(m_context.getRenderContext(), 0, 0, testImg.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
		}

		// Draw reference
		for (int y = 0; y < refImg.getHeight(); y++)
		{
			for (int x = 0; x < refImg.getWidth(); x++)
			{
				const float			xf			= (float(x)+.5f) / float(refImg.getWidth());
				const float			yf			= (float(refImg.getHeight()-y-1)+.5f) / float(refImg.getHeight());
				const float			oow			= ((xf + yf) < 1.0f)
												? projectedTriInterpolate(tcu::Vec3(w[0], w[1], w[2]), tcu::Vec3(w[0], w[1], w[2]), xf, yf)
												: projectedTriInterpolate(tcu::Vec3(w[3], w[2], w[1]), tcu::Vec3(w[3], w[2], w[1]), 1.0f-xf, 1.0f-yf);
				const tcu::Vec4		color		(0.0f, oow - 1.0f, 0.0f, 1.0f);

				refImg.setPixel(x, y, tcu::RGBA(color));
			}
		}

		// Compare
		{
			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									isOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}
};

class PointCoordCase : public TestCase
{
public:
	PointCoordCase (Context& context)
		: TestCase(context, "pointcoord", "gl_PointCoord Test")
	{
	}

	IterateResult iterate (void)
	{
		TestLog&				log			= m_testCtx.getLog();
		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
		const int				width		= de::min(256, m_context.getRenderTarget().getWidth());
		const int				height		= de::min(256, m_context.getRenderTarget().getHeight());
		const float				threshold	= 0.02f;

		const int				numPoints	= 8;

		vector<tcu::Vec3>		coords		(numPoints);
		float					pointSizeRange[2]	= { 0.0f, 0.0f };

		de::Random				rnd			(0x145fa);
		tcu::Surface			testImg		(width, height);
		tcu::Surface			refImg		(width, height);

		gl.getFloatv(GL_ALIASED_POINT_SIZE_RANGE, &pointSizeRange[0]);
		GLU_EXPECT_NO_ERROR(gl.getError(), "glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE)");

		if (pointSizeRange[0] <= 0.0f || pointSizeRange[1] <= 0.0f || pointSizeRange[1] < pointSizeRange[0])
			throw tcu::TestError("Invalid GL_ALIASED_POINT_SIZE_RANGE");

		// Compute coordinates.
		{

			for (vector<tcu::Vec3>::iterator coord = coords.begin(); coord != coords.end(); ++coord)
			{
				coord->x() = rnd.getFloat(-0.9f, 0.9f);
				coord->y() = rnd.getFloat(-0.9f, 0.9f);
				coord->z() = rnd.getFloat(pointSizeRange[0], pointSizeRange[1]);
			}
		}

		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
			"#version 300 es\n"
			"in highp vec3 a_positionSize;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = vec4(a_positionSize.xy, 0.0, 1.0);\n"
			"	gl_PointSize = a_positionSize.z;\n"
			"}\n",

			"#version 300 es\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	o_color = vec4(gl_PointCoord, 0.0, 1.0);\n"
			"}\n"));

		log << program;

		if (!program.isOk())
			throw tcu::TestError("Compile failed");

		// Draw with GL.
		{
			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_positionSize", 3, (int)coords.size(), 0, (const float*)&coords[0]);
			const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
			const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);

			gl.viewport(viewportX, viewportY, width, height);
			gl.clearColor(0.0f, 0.0f, 0.0f, 1.0f);
			gl.clear(GL_COLOR_BUFFER_BIT);

			gl.useProgram(program.getProgram());

			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
					  glu::pr::Points((int)coords.size()));

			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
		}

		// Draw reference
		tcu::clear(refImg.getAccess(), tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f));
		for (vector<tcu::Vec3>::const_iterator pointIter = coords.begin(); pointIter != coords.end(); ++pointIter)
		{
			const int	x0		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) - pointIter->z()*0.5f);
			const int	y0		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) - pointIter->z()*0.5f);
			const int	x1		= deRoundFloatToInt32(float(width) *(pointIter->x()*0.5f + 0.5f) + pointIter->z()*0.5f);
			const int	y1		= deRoundFloatToInt32(float(height)*(pointIter->y()*0.5f + 0.5f) + pointIter->z()*0.5f);
			const int	w		= x1-x0;
			const int	h		= y1-y0;

			for (int yo = 0; yo < h; yo++)
			{
				for (int xo = 0; xo < w; xo++)
				{
					const float			xf		= float(xo+0.5f) / float(w);
					const float			yf		= float((h-yo-1)+0.5f) / float(h);
					const tcu::Vec4		color	(xf, yf, 0.0f, 1.0f);
					const int			dx		= x0+xo;
					const int			dy		= y0+yo;

					if (de::inBounds(dx, 0, refImg.getWidth()) && de::inBounds(dy, 0, refImg.getHeight()))
						refImg.setPixel(dx, dy, tcu::RGBA(color));
				}
			}
		}

		// Compare
		{
			bool isOk = tcu::fuzzyCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									isOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}
};

class FrontFacingCase : public TestCase
{
public:
	FrontFacingCase (Context& context)
		: TestCase(context, "frontfacing", "gl_FrontFacing Test")
	{
	}

	IterateResult iterate (void)
	{
		// Test case renders two adjecent quads, where left is has front-facing
		// triagles and right back-facing. Color is selected based on gl_FrontFacing
		// value.

		TestLog&				log			= m_testCtx.getLog();
		const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
		de::Random				rnd			(0x89f2c);
		const int				width		= de::min(64, m_context.getRenderTarget().getWidth());
		const int				height		= de::min(64, m_context.getRenderTarget().getHeight());
		const int				viewportX	= rnd.getInt(0, m_context.getRenderTarget().getWidth()-width);
		const int				viewportY	= rnd.getInt(0, m_context.getRenderTarget().getHeight()-height);
		const tcu::RGBA			threshold	= tcu::RGBA(1,1,1,1) + m_context.getRenderTarget().getPixelFormat().getColorThreshold();

		tcu::Surface			testImg		(width, height);
		tcu::Surface			refImg		(width, height);

		const glu::ShaderProgram program(m_context.getRenderContext(), glu::makeVtxFragSources(
			"#version 300 es\n"
			"in highp vec4 a_position;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"}\n",

			"#version 300 es\n"
			"layout(location = 0) out mediump vec4 o_color;\n"
			"void main (void)\n"
			"{\n"
			"	if (gl_FrontFacing)\n"
			"		o_color = vec4(0.0, 1.0, 0.0, 1.0);\n"
			"	else\n"
			"		o_color = vec4(0.0, 0.0, 1.0, 1.0);\n"
			"}\n"));

		log << program;

		if (!program.isOk())
			throw tcu::TestError("Compile failed");

		// Draw with GL.
		{
			const float positions[] =
			{
				-1.0f,  1.0f, 0.0f, 1.0f,
				-1.0f, -1.0f, 0.0f, 1.0f,
				 1.0f,  1.0f, 0.0f, 1.0f,
				 1.0f, -1.0f, 0.0f, 1.0f
			};
			const deUint16 indicesCCW[]	= { 0, 1, 2, 2, 1, 3 };
			const deUint16 indicesCW[]	= { 2, 1, 0, 3, 1, 2 };

			glu::VertexArrayBinding	posBinding	= glu::va::Float("a_position", 4, 4, 0, &positions[0]);

			gl.useProgram(program.getProgram());

			gl.viewport(viewportX, viewportY, width/2, height);
			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCCW), &indicesCCW[0]));

			gl.viewport(viewportX + width/2, viewportY, width-width/2, height);
			glu::draw(m_context.getRenderContext(), program.getProgram(), 1, &posBinding,
					  glu::pr::Triangles(DE_LENGTH_OF_ARRAY(indicesCW), &indicesCW[0]));

			glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
			GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");
		}

		// Draw reference
		for (int y = 0; y < refImg.getHeight(); y++)
		{
			for (int x = 0; x < refImg.getWidth()/2; x++)
				refImg.setPixel(x, y, tcu::RGBA::green);

			for (int x = refImg.getWidth()/2; x < refImg.getWidth(); x++)
				refImg.setPixel(x, y, tcu::RGBA::blue);
		}

		// Compare
		{
			bool isOk = tcu::pixelThresholdCompare(log, "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT);
			m_testCtx.setTestResult(isOk ? QP_TEST_RESULT_PASS	: QP_TEST_RESULT_FAIL,
									isOk ? "Pass"				: "Image comparison failed");
		}

		return STOP;
	}
};

// VertexIDCase

class VertexIDCase : public TestCase
{
public:
						VertexIDCase			(Context& context);
						~VertexIDCase			(void);

	void				init					(void);
	void				deinit					(void);
	IterateResult		iterate					(void);

private:
	enum
	{
		MAX_VERTICES = 8*3	//!< 8 triangles, totals 24 vertices
	};

	void				renderReference			(const tcu::PixelBufferAccess& dst, const int numVertices, const deUint16* const indices, const tcu::Vec4* const positions, const tcu::Vec4* const colors);

	glu::ShaderProgram*	m_program;
	deUint32			m_positionBuffer;
	deUint32			m_elementBuffer;

	vector<tcu::Vec4>	m_positions;
	vector<tcu::Vec4>	m_colors;
	int					m_viewportW;
	int					m_viewportH;

	int					m_iterNdx;
};

VertexIDCase::VertexIDCase (Context& context)
	: TestCase			(context, "vertex_id",	"gl_VertexID Test")
	, m_program			(DE_NULL)
	, m_positionBuffer	(0)
	, m_elementBuffer	(0)
	, m_viewportW		(0)
	, m_viewportH		(0)
	, m_iterNdx			(0)
{
}

VertexIDCase::~VertexIDCase (void)
{
	VertexIDCase::deinit();
}

void VertexIDCase::init (void)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	const int				width		= m_context.getRenderTarget().getWidth();
	const int				height		= m_context.getRenderTarget().getHeight();

	const int				quadWidth	= 32;
	const int				quadHeight	= 32;

	if (width < quadWidth)
		throw tcu::NotSupportedError("Too small render target");

	const int				maxQuadsX	= width/quadWidth;
	const int				numVertices	= MAX_VERTICES;

	const int				numQuads	= numVertices/6 + (numVertices%6 != 0 ? 1 : 0);
	const int				viewportW	= de::min(numQuads, maxQuadsX)*quadWidth;
	const int				viewportH	= (numQuads/maxQuadsX + (numQuads%maxQuadsX != 0 ? 1 : 0))*quadHeight;

	if (viewportH > height)
		throw tcu::NotSupportedError("Too small render target");

	DE_ASSERT(viewportW <= width && viewportH <= height);

	DE_ASSERT(!m_program);
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::makeVtxFragSources(
		"#version 300 es\n"
		"in highp vec4 a_position;\n"
		"out mediump vec4 v_color;\n"
		"uniform highp vec4 u_colors[24];\n"
		"void main (void)\n"
		"{\n"
		"	gl_Position = a_position;\n"
		"	v_color = u_colors[gl_VertexID];\n"
		"}\n",

		"#version 300 es\n"
		"in mediump vec4 v_color;\n"
		"layout(location = 0) out mediump vec4 o_color;\n"
		"void main (void)\n"
		"{\n"
		"	o_color = v_color;\n"
		"}\n"));

	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
	{
		delete m_program;
		m_program = DE_NULL;
		throw tcu::TestError("Compile failed");
	}

	gl.genBuffers(1, &m_positionBuffer);
	gl.genBuffers(1, &m_elementBuffer);

	// Set colors (in dynamic memory to save static data space).
	m_colors.resize(numVertices);
	m_colors[ 0] = tcu::Vec4(0.0f, 0.0f, 0.0f, 1.0f);
	m_colors[ 1] = tcu::Vec4(0.5f, 1.0f, 0.5f, 1.0f);
	m_colors[ 2] = tcu::Vec4(0.0f, 0.5f, 1.0f, 1.0f);
	m_colors[ 3] = tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
	m_colors[ 4] = tcu::Vec4(0.0f, 1.0f, 1.0f, 1.0f);
	m_colors[ 5] = tcu::Vec4(0.5f, 0.0f, 0.0f, 1.0f);
	m_colors[ 6] = tcu::Vec4(0.5f, 0.0f, 1.0f, 1.0f);
	m_colors[ 7] = tcu::Vec4(0.5f, 0.0f, 0.5f, 1.0f);
	m_colors[ 8] = tcu::Vec4(1.0f, 0.0f, 0.0f, 1.0f);
	m_colors[ 9] = tcu::Vec4(0.5f, 1.0f, 0.0f, 1.0f);
	m_colors[10] = tcu::Vec4(0.0f, 0.5f, 0.0f, 1.0f);
	m_colors[11] = tcu::Vec4(0.5f, 1.0f, 1.0f, 1.0f);
	m_colors[12] = tcu::Vec4(0.0f, 0.0f, 1.0f, 1.0f);
	m_colors[13] = tcu::Vec4(1.0f, 0.0f, 0.5f, 1.0f);
	m_colors[14] = tcu::Vec4(0.0f, 0.5f, 0.5f, 1.0f);
	m_colors[15] = tcu::Vec4(1.0f, 1.0f, 0.5f, 1.0f);
	m_colors[16] = tcu::Vec4(1.0f, 0.0f, 1.0f, 1.0f);
	m_colors[17] = tcu::Vec4(1.0f, 0.5f, 0.0f, 1.0f);
	m_colors[18] = tcu::Vec4(0.0f, 1.0f, 0.5f, 1.0f);
	m_colors[19] = tcu::Vec4(1.0f, 0.5f, 1.0f, 1.0f);
	m_colors[20] = tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);
	m_colors[21] = tcu::Vec4(1.0f, 0.5f, 0.5f, 1.0f);
	m_colors[22] = tcu::Vec4(0.0f, 0.0f, 0.5f, 1.0f);
	m_colors[23] = tcu::Vec4(1.0f, 1.0f, 1.0f, 1.0f);

	// Compute positions.
	m_positions.resize(numVertices);
	DE_ASSERT(numVertices%3 == 0);
	for (int vtxNdx = 0; vtxNdx < numVertices; vtxNdx += 3)
	{
		const float	h			= 2.0f * float(quadHeight)/float(viewportH);
		const float	w			= 2.0f * float(quadWidth)/float(viewportW);

		const int	triNdx		= vtxNdx/3;
		const int	quadNdx		= triNdx/2;
		const int	quadY		= quadNdx/maxQuadsX;
		const int	quadX		= quadNdx%maxQuadsX;

		const float	x0			= -1.0f + quadX*w;
		const float	y0			= -1.0f + quadY*h;

		if (triNdx%2 == 0)
		{
			m_positions[vtxNdx+0] = tcu::Vec4(x0,   y0,   0.0f, 1.0f);
			m_positions[vtxNdx+1] = tcu::Vec4(x0+w, y0+h, 0.0f, 1.0f);
			m_positions[vtxNdx+2] = tcu::Vec4(x0,   y0+h, 0.0f, 1.0f);
		}
		else
		{
			m_positions[vtxNdx+0] = tcu::Vec4(x0+w, y0+h, 0.0f, 1.0f);
			m_positions[vtxNdx+1] = tcu::Vec4(x0,   y0,   0.0f, 1.0f);
			m_positions[vtxNdx+2] = tcu::Vec4(x0+w, y0,   0.0f, 1.0f);
		}
	}

	m_viewportW	= viewportW;
	m_viewportH	= viewportH;
	m_iterNdx	= 0;

	m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
}

void VertexIDCase::deinit (void)
{
	delete m_program;
	m_program = DE_NULL;

	if (m_positionBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_positionBuffer);
		m_positionBuffer = 0;
	}

	if (m_elementBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_elementBuffer);
		m_elementBuffer = 0;
	}

	m_positions.clear();
	m_colors.clear();
}

class VertexIDReferenceShader : public rr::VertexShader, public rr::FragmentShader
{
public:
	enum
	{
		VARYINGLOC_COLOR = 0
	};

	VertexIDReferenceShader ()
		: rr::VertexShader	(2, 1)		// color and pos in => color out
		, rr::FragmentShader(1, 1)		// color in => color out
	{
		this->rr::VertexShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
		this->rr::VertexShader::m_inputs[1].type		= rr::GENERICVECTYPE_FLOAT;

		this->rr::VertexShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
		this->rr::VertexShader::m_outputs[0].flatshade	= false;

		this->rr::FragmentShader::m_inputs[0].type		= rr::GENERICVECTYPE_FLOAT;
		this->rr::FragmentShader::m_inputs[0].flatshade	= false;

		this->rr::FragmentShader::m_outputs[0].type		= rr::GENERICVECTYPE_FLOAT;
	}

	void shadeVertices (const rr::VertexAttrib* inputs, rr::VertexPacket* const* packets, const int numPackets) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		{
			const int positionAttrLoc = 0;
			const int colorAttrLoc = 1;

			rr::VertexPacket& packet = *packets[packetNdx];

			// Transform to position
			packet.position = rr::readVertexAttribFloat(inputs[positionAttrLoc], packet.instanceNdx, packet.vertexNdx);

			// Pass color to FS
			packet.outputs[VARYINGLOC_COLOR] = rr::readVertexAttribFloat(inputs[colorAttrLoc], packet.instanceNdx, packet.vertexNdx);
		}
	}

	void shadeFragments (rr::FragmentPacket* packets, const int numPackets, const rr::FragmentShadingContext& context) const
	{
		for (int packetNdx = 0; packetNdx < numPackets; ++packetNdx)
		{
			rr::FragmentPacket& packet = packets[packetNdx];

			for (int fragNdx = 0; fragNdx < 4; ++fragNdx)
				rr::writeFragmentOutput(context, packetNdx, fragNdx, 0, rr::readVarying<float>(packet, context, VARYINGLOC_COLOR, fragNdx));
		}
	}
};

void VertexIDCase::renderReference (const tcu::PixelBufferAccess& dst, const int numVertices, const deUint16* const indices, const tcu::Vec4* const positions, const tcu::Vec4* const colors)
{
	const rr::Renderer				referenceRenderer;
	const rr::RenderState			referenceState		((rr::ViewportState)(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(dst)));
	const rr::RenderTarget			referenceTarget		(rr::MultisamplePixelBufferAccess::fromSinglesampleAccess(dst));
	const VertexIDReferenceShader	referenceShader;
	      rr::VertexAttrib			attribs[2];

	attribs[0].type				= rr::VERTEXATTRIBTYPE_FLOAT;
	attribs[0].size				= 4;
	attribs[0].stride			= 0;
	attribs[0].instanceDivisor	= 0;
	attribs[0].pointer			= positions;

	attribs[1].type				= rr::VERTEXATTRIBTYPE_FLOAT;
	attribs[1].size				= 4;
	attribs[1].stride			= 0;
	attribs[1].instanceDivisor	= 0;
	attribs[1].pointer			= colors;

	referenceRenderer.draw(
		rr::DrawCommand(
			referenceState,
			referenceTarget,
			rr::Program(&referenceShader, &referenceShader),
			2,
			attribs,
			rr::PrimitiveList(rr::PRIMITIVETYPE_TRIANGLES, numVertices, rr::DrawIndices(indices))));
}

VertexIDCase::IterateResult VertexIDCase::iterate (void)
{
	const glw::Functions&	gl			= m_context.getRenderContext().getFunctions();
	const int				width		= m_context.getRenderTarget().getWidth();
	const int				height		= m_context.getRenderTarget().getHeight();
	const int				viewportW	= m_viewportW;
	const int				viewportH	= m_viewportH;

	const float				threshold	= 0.02f;

	de::Random				rnd			(0xcf23ab1 ^ deInt32Hash(m_iterNdx));
	tcu::Surface			refImg		(viewportW, viewportH);
	tcu::Surface			testImg		(viewportW, viewportH);

	const int				viewportX	= rnd.getInt(0, width-viewportW);
	const int				viewportY	= rnd.getInt(0, height-viewportH);

	const int				posLoc		= gl.getAttribLocation(m_program->getProgram(), "a_position");
	const int				colorsLoc	= gl.getUniformLocation(m_program->getProgram(), "u_colors[0]");
	const tcu::Vec4			clearColor	(0.0f, 0.0f, 0.0f, 1.0f);

	// Setup common state.
	gl.viewport					(viewportX, viewportY, viewportW, viewportH);
	gl.useProgram				(m_program->getProgram());
	gl.bindBuffer				(GL_ARRAY_BUFFER, m_positionBuffer);
	gl.enableVertexAttribArray	(posLoc);
	gl.vertexAttribPointer		(posLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
	gl.uniform4fv				(colorsLoc, (int)m_colors.size(), (const float*)&m_colors[0]);

	// Clear render target to black.
	gl.clearColor	(clearColor.x(), clearColor.y(), clearColor.z(), clearColor.w());
	gl.clear		(GL_COLOR_BUFFER_BIT);

	tcu::clear(refImg.getAccess(), clearColor);

	if (m_iterNdx == 0)
	{
		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter0", "glDrawArrays()");
		vector<deUint16>		indices		(m_positions.size());

		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &m_positions[0], GL_DYNAMIC_DRAW);
		gl.drawArrays(GL_TRIANGLES, 0, (int)m_positions.size());

		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");

		// Reference indices
		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
			indices[ndx] = (deUint16)ndx;

		renderReference(refImg.getAccess(), (int)m_positions.size(), &indices[0], &m_positions[0], &m_colors[0]);
	}
	else if (m_iterNdx == 1)
	{
		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter1", "glDrawElements(), indices in client-side array");
		vector<deUint16>		indices		(m_positions.size());
		vector<tcu::Vec4>		mappedPos	(m_positions.size());

		// Compute initial indices and suffle
		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
			indices[ndx] = (deUint16)ndx;
		rnd.shuffle(indices.begin(), indices.end());

		// Use indices to re-map positions.
		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
			mappedPos[indices[ndx]] = m_positions[ndx];

		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &mappedPos[0], GL_DYNAMIC_DRAW);
		gl.drawElements(GL_TRIANGLES, (int)indices.size(), GL_UNSIGNED_SHORT, &indices[0]);

		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");

		renderReference(refImg.getAccess(), (int)indices.size(), &indices[0], &mappedPos[0], &m_colors[0]);
	}
	else if (m_iterNdx == 2)
	{
		tcu::ScopedLogSection	logSection	(m_testCtx.getLog(), "Iter2", "glDrawElements(), indices in buffer");
		vector<deUint16>		indices		(m_positions.size());
		vector<tcu::Vec4>		mappedPos	(m_positions.size());

		// Compute initial indices and suffle
		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
			indices[ndx] = (deUint16)ndx;
		rnd.shuffle(indices.begin(), indices.end());

		// Use indices to re-map positions.
		for (int ndx = 0; ndx < (int)indices.size(); ndx++)
			mappedPos[indices[ndx]] = m_positions[ndx];

		gl.bindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_elementBuffer);
		gl.bufferData(GL_ELEMENT_ARRAY_BUFFER, (int)(indices.size()*sizeof(deUint16)), &indices[0], GL_DYNAMIC_DRAW);

		gl.bufferData(GL_ARRAY_BUFFER, (int)(m_positions.size()*sizeof(tcu::Vec4)), &mappedPos[0], GL_DYNAMIC_DRAW);
		gl.drawElements(GL_TRIANGLES, (int)indices.size(), GL_UNSIGNED_SHORT, DE_NULL);

		glu::readPixels(m_context.getRenderContext(), viewportX, viewportY, testImg.getAccess());
		GLU_EXPECT_NO_ERROR(gl.getError(), "Draw");

		tcu::clear(refImg.getAccess(), clearColor);
		renderReference(refImg.getAccess(), (int)indices.size(), &indices[0], &mappedPos[0], &m_colors[0]);
	}
	else
		DE_ASSERT(false);

	if (!tcu::fuzzyCompare(m_testCtx.getLog(), "Result", "Image comparison result", refImg, testImg, threshold, tcu::COMPARE_LOG_RESULT))
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image comparison failed");

	m_iterNdx += 1;
	return (m_iterNdx < 3) ? CONTINUE : STOP;
}

ShaderBuiltinVarTests::ShaderBuiltinVarTests (Context& context)
	: TestCaseGroup(context, "builtin_variable", "Built-in Variable Tests")
{
}

ShaderBuiltinVarTests::~ShaderBuiltinVarTests (void)
{
}

void ShaderBuiltinVarTests::init (void)
{
	// Builtin constants.

	static const struct
	{
		const char*											caseName;
		const char*											varName;
		ShaderBuiltinConstantCase::GetConstantValueFunc		getValue;
	} builtinConstants[] =
	{
		// GLES 2.

		{ "max_vertex_attribs",					"gl_MaxVertexAttribs",				getInteger<GL_MAX_VERTEX_ATTRIBS>						},
		{ "max_vertex_uniform_vectors",			"gl_MaxVertexUniformVectors",		getInteger<GL_MAX_VERTEX_UNIFORM_VECTORS>				},
		{ "max_fragment_uniform_vectors",		"gl_MaxFragmentUniformVectors",		getInteger<GL_MAX_FRAGMENT_UNIFORM_VECTORS>				},
		{ "max_texture_image_units",			"gl_MaxTextureImageUnits",			getInteger<GL_MAX_TEXTURE_IMAGE_UNITS>					},
		{ "max_vertex_texture_image_units",		"gl_MaxVertexTextureImageUnits",	getInteger<GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS>			},
		{ "max_combined_texture_image_units",	"gl_MaxCombinedTextureImageUnits",	getInteger<GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS>			},
		{ "max_draw_buffers",					"gl_MaxDrawBuffers",				getInteger<GL_MAX_DRAW_BUFFERS>							},

		// GLES 3.

		{ "max_vertex_output_vectors",			"gl_MaxVertexOutputVectors",		getVectorsFromComps<GL_MAX_VERTEX_OUTPUT_COMPONENTS>	},
		{ "max_fragment_input_vectors",			"gl_MaxFragmentInputVectors",		getVectorsFromComps<GL_MAX_FRAGMENT_INPUT_COMPONENTS>	},
		{ "min_program_texel_offset",			"gl_MinProgramTexelOffset",			getInteger<GL_MIN_PROGRAM_TEXEL_OFFSET>					},
		{ "max_program_texel_offset",			"gl_MaxProgramTexelOffset",			getInteger<GL_MAX_PROGRAM_TEXEL_OFFSET>					}
	};

	for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(builtinConstants); ndx++)
	{
		const char* const										caseName	= builtinConstants[ndx].caseName;
		const char* const										varName		= builtinConstants[ndx].varName;
		const ShaderBuiltinConstantCase::GetConstantValueFunc	getValue	= builtinConstants[ndx].getValue;

		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_vertex").c_str(),	varName, varName, getValue, glu::SHADERTYPE_VERTEX));
		addChild(new ShaderBuiltinConstantCase(m_context, (string(caseName) + "_fragment").c_str(),	varName, varName, getValue, glu::SHADERTYPE_FRAGMENT));
	}

	addChild(new ShaderDepthRangeTest(m_context, "depth_range_vertex",		"gl_DepthRange", true));
	addChild(new ShaderDepthRangeTest(m_context, "depth_range_fragment",	"gl_DepthRange", false));

	// Vertex shader builtin variables.
	addChild(new VertexIDCase		(m_context));
	// \todo [2013-03-20 pyry] gl_InstanceID -- tested in instancing tests quite thoroughly.

	// Fragment shader builtin variables.

	addChild(new FragCoordXYZCase	(m_context));
	addChild(new FragCoordWCase		(m_context));
	addChild(new PointCoordCase		(m_context));
	addChild(new FrontFacingCase	(m_context));
}

} // Functional
} // gles3
} // deqp
